﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media;

namespace N_m3u8DL_CLI_SimpleG
{
    public class ClipboardMonitor : IDisposable
    {
        #region 消息/API 定义
        // MSDN http://msdn.microsoft.com/en-us/library/windows/desktop/ms649033%28v=vs.85%29.aspx
        // 该 API 函数只能用于 Vista 及以上版本

        private const int WM_CLIPBOARDUPDATE = 0x031D;

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        public static extern bool AddClipboardFormatListener(IntPtr hwnd);

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        public static extern bool RemoveClipboardFormatListener(IntPtr hwnd);
        #endregion

        #region 事件声明
        public class ClipboardEventArgs : EventArgs
        {
            public ClipboardEventArgs() { }

            public ClipboardEventArgs(string text)
            {
                Text = text;
            }

            public string Text { get; }
        }

        public delegate void ClipboardChangedEvent(ClipboardEventArgs e);
        #endregion

        HwndSource FSource;

        public event ClipboardChangedEvent ContentChanged;

        public void Start(Visual form)
        {
            if (FSource == null)
            {
                FSource = PresentationSource.FromVisual(form) as HwndSource;
                // 监听剪贴板
                if (AddClipboardFormatListener(FSource.Handle))
                    FSource.AddHook(WndProc);
            }
        }

        public void Stop()
        {
            if (FSource != null)
            {
                // 移除监听
                RemoveClipboardFormatListener(FSource.Handle);
                FSource.RemoveHook(WndProc);
                FSource = null;
            }
        }

        public bool IsRunning => FSource != null;

        public void Dispose()
        {
            Stop();
        }

        /// <summary>
        /// WndProc 监听剪贴板变更通知
        /// </summary>
        /// <param name="hwnd"></param>
        /// <param name="msg"></param>
        /// <param name="wParam"></param>
        /// <param name="lParam"></param>
        /// <param name="handled"></param>
        /// <returns></returns>
        IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
        {
            switch (msg)
            {
                case WM_CLIPBOARDUPDATE:
                    {
                        // 直接调用 System.Windows.Clipboard.GetText() 会有点小问题
                        var s = ReadClipboardText();
                        ContentChanged?.Invoke(new ClipboardEventArgs(s));
                    }
                    break;
                default:
                    break;
            }

            return IntPtr.Zero;
        }

        // 如果直接通过 Clipboard.GetText() 获取剪贴板上的文本时
        // 如果剪贴板被占用（每次只能一个程序读取），则会报 OpenClipboard HRESULT:0x800401D0 (CLIPBRD_E_CANT_OPEN)) 错误
        // 这是个随机错误，比较隐蔽，而且在 WinForm 中没有这个问题
        // 剪贴板被其它程序占用是个小概率事件，且占用时间通常很短，这里每次读取时多试几次就可以了
        string ReadClipboardText()
        {
            //for (var i = 0; i < 100; i++)
            while (true)
            {
                try
                {
                    return Clipboard.GetText();
                }
                catch
                {
                    Thread.Sleep(10);
                }
            }
        }
    }
}
